#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<ctype.h>
#include<sys/stat.h>
#include<fcntl.h>

using namespace std;

const int basesize = 1024;
const int argvnum = 64;
const int envnum = 64;

//全局的命令行参数
char *gargv[argvnum];
//计数器
int gargc = 0;

//全局变量
int lastcode = 0;

//我的系统的环境变量
char *genv[envnum];

//全局的当前shell工作路径
char pwd[basesize];
char pwdenv[basesize];

// 全局变量与重定向有关
#define NoneRedir    0 //无重定向
#define InputRedir   1 //输入重定向
#define OutputRedir  2 //输出重定向
#define AppRedir     3 //追加重定向

//重定向类型
int redir = NoneRedir;
//重定向指向的文件名
char *filename = nullptr;

//去掉空格的宏
#define TrimSpace(pos) do{\
    while(isspace(*pos)){\
    pos++;\
    }\
}while(0)


string GetUserName()
{
    string name = getenv("USER");
    return name.empty() ? "None" : name;
}

string GetHostName()
{
    string hostname = getenv("HOSTNAME");
    return hostname.empty() ? "None" : hostname;
}

string GetPwd()
{
    if(nullptr == getcwd(pwd,sizeof(pwd))) return "None";
    //更改环境变量pwd
    snprintf(pwdenv,sizeof(pwdenv),"PWD=%s",pwd);
    putenv(pwdenv);
    return pwd;

    //string pwd = getenv("PWD");
    //return pwd.empty() ? "None" : pwd;
    
}

string LastDir()
{
    string curr = GetPwd();
    if(curr == "/" || curr == "None") return curr;

    //   /home/jyz/linux
    size_t pos = curr.rfind("/");
    if(pos == std::string::npos) return curr;
    return curr.substr(pos+1);
}

string MakeCommandLine()
{
    char command_line[basesize];
    snprintf(command_line,basesize,"[%s@%s %s]# ",\
            GetUserName().c_str(),GetHostName().c_str(),LastDir().c_str());
    return command_line;
}

// 1.命令行提示符
void PrintCommandLine()
{
    printf("%s",MakeCommandLine().c_str());
    //刷新缓冲区
    fflush(stdout);
}

bool GetCommandLine(char command_buffer[], int size)  //2.获取用户命令
{
    //我们认为，我们要将用户 输入的命令，当作一个完整的字符串
    //"ls -a -l"
    char *result = fgets(command_buffer,size,stdin);
    if(!result)
    {
        return false;
    }
    command_buffer[strlen(command_buffer)-1] = 0;
    if(strlen(command_buffer) == 0) return false;
    return true;
}

void ResetCommandline()
{
    memset(gargv,0,sizeof(gargv));
    gargc = 0;

    // 重定向 (默认值)
    redir = NoneRedir;
    filename = nullptr;
}

void ParseRedir(char command_buffer[],int len)
{

    // "ls -a -l -n"
    // "ls -a -l -n" > file.txt
    // "ls -a -l -n" < file.txt
    // "ls -a -l -n" >> file.txt
    int end = len - 1;
    while(end >= 0)
    {
        if(command_buffer[end] == '<')
        {
            //输入重定向
            redir = InputRedir;
            command_buffer[end] = 0;//将< 置为\0
            filename = &command_buffer[end] + 1;
            //过滤空格
            TrimSpace(filename);
            break;
        }
        else if(command_buffer[end] == '>')
        {
            if(command_buffer[end-1] == '>')
            {
                redir = AppRedir;
                command_buffer[end] = 0;//将两个>>置为\0
                command_buffer[end-1] = 0;
                filename = &command_buffer[end]+1;
                TrimSpace(filename);
                break;
            }
            else
            {
                redir = OutputRedir;
                command_buffer[end] = 0;

                filename = &command_buffer[end]+1;
                TrimSpace(filename);
                break;
            }
        }
        else
        {
            end--;
        }
    }
}

void ParseCommand(char command_buffer[])
{

    const char *sep = " ";
    gargv[gargc++] = strtok(command_buffer,sep);
    
    while((bool)(gargv[gargc++] = strtok(nullptr,sep)));
    gargc--;
}

void ParseCommandLine(char command_buffer[],int len) //3.分析命令
{
    (void)len;
    ResetCommandline();
    ParseRedir(command_buffer,len);
    ParseCommand(command_buffer);
    //测试
    //printf("command start: %s\n",command_buffer);


    //测试
    //printf("redir:%d\n",redir);
    //printf("filename:%s\n",filename);
    //printf("command end：%s\n",command_buffer);


}

void debug()
{
    printf("argc: %d\n",gargc);
    for(int i = 0;gargv[i];i++)
    {
        printf("argv[%d]: %s\n",i,gargv[i]);
    }
}

void DoRedir()
{

    // 1.重定向应该让子进程自己做
    // 2.程序替换会不会影响重定向
    //先判断重定向
    if(redir == InputRedir)
    {
        if(filename)//filename需要是一个合法的文件名
        {
            int fd = open(filename,O_RDONLY);
            if(fd < 0)
            {
                exit(2);
            }
            dup2(fd,0);//标准输入，从0->fd号文件读取
        }
        else
        {
            exit(1);
        }
        
    }
    else if(redir == OutputRedir)
    {
        //输出重定向，文件不存在要新建，文件存在要清空文件内容
        if(filename)
        {
            int fd = open(filename,O_CREAT | O_WRONLY | O_TRUNC, 0666);
            if(fd < 0)
            {
                exit(4);
            }
            dup2(fd,1);//由输出到显示器变为输出到文件中
        }
        else
        {
            exit(3);
        }
    }
    else if(redir == AppRedir)
    {
        //追加，没有时要新建，且要写入和追加
        if(filename)
        {
            int fd = open(filename,O_CREAT | O_WRONLY | O_APPEND, 0666);
            if(fd < 0)
            {
                exit(6);
            }
            dup2(fd,1);
        }
        else
        {
            exit(5);
        }
    }
    else
    {
        //没有重定向
    }
}

//在shell中
// 有些命令，必须由子进程来执行
// 有些命令，不能由子进程执行，要由shell自己执行 --- 内建命令 built command
bool ExecuteCommand() //4.执行命令
{
    //让子进程执行
    pid_t id =fork();
    if(id < 0) return false;
    if(id == 0)
    {
        //子进程
        DoRedir();
        //1.执行命令
        execvpe(gargv[0],gargv,genv);
        //exec*
        //2.退出
        exit(7);
    }
    int status = 0;
    pid_t rid = waitpid(id,&status,0);
    if(rid > 0)
    {
        //退出信息
        if(WIFEXITED(status))
        {
            //获取退出码
            lastcode = WEXITSTATUS(status);
        }
        else
        {
            //代码没跑完，出异常
            lastcode = 100;
        }
        return true;
    }
    return false;
}

void AddEnv(const char *item)
{
    int index = 0;
    while(genv[index])
    {
        index++;
    }

    genv[index] = (char*)malloc(strlen(item)+1);
    strncpy(genv[index],item,strlen(item)+1);
    genv[++index] = nullptr;
}

//shell自己执行命令，本质是shell调用自己的函数
bool CheckAndExecBuiltCommand()
{
    if(strcmp(gargv[0],"cd") == 0)
    {
        //内建命令
        //argc=2,cd后面+路径
        if(gargc == 2)
        {
            chdir(gargv[1]);
            lastcode = 0;
        }
        else
        {
            lastcode = 1;
        }
        return true;
    }
    else if(strcmp(gargv[0],"export") == 0)
    {
        //export也是内建命令
        if(gargc == 2)
        {
            AddEnv(gargv[1]);
            lastcode = 0;
        }
        else
        {
            lastcode = 2;
        }
        return true;
    }
    else if(strcmp(gargv[0],"env") == 0)
    {
        for(int i=0; genv[i];i++)
        {
            printf("%s\n",genv[i]);
        }
        lastcode = 0;
        return true;
    }
    else if(strcmp("echo",gargv[0])==0)
    {
        if(gargc == 2)
        {
            // echo $?
            // echo $PATH
            // echo hello
            if(gargv[1][0] == '$')
            {
                if(gargv[1][1] == '?')
                {
                    printf("%d\n",lastcode);
                    lastcode = 0;
                }
            }
            else
            {
                printf("%s\n",gargv[1]);
                lastcode = 0;
            }
        }
        else
        {
            lastcode = 3;
        }
        return true;
    }
    return false;
}

//作为一个shell，获取环境变量应该从系统的配置来
//我们今天就直接从父shell中获取环境变量
void InitEnv()
{
    //获取shell环境变量
    extern char **environ;
    int index = 0;
    while(environ[index])
    {
        genv[index] = (char*)malloc(strlen(environ[index])+1);
        strncpy(genv[index],environ[index],strlen(environ[index])+1);
        index++;
    }
    genv[index] = nullptr;
}


int main()
{
    //初始化env
    InitEnv();

    char command_buffer[basesize];

    while(true)
    {
       PrintCommandLine(); //1.命令行提示符
        
      // printf("\n");
       //sleep(100);
       if(! GetCommandLine(command_buffer,basesize)) //2.获取用户命令
       {
           continue;
       }

       //printf("%s\n",command_buffer);
        //"ls -a -l -b -c "-->"ls" "-a" "-l" "-b" "-c"
        ParseCommandLine(command_buffer,strlen(command_buffer)); //3.分析命令
        
        //debug();
        
        //检测是否为内建命令，如果是，不往下走（下面的是让子进程执行）
        if(CheckAndExecBuiltCommand())
        {
            continue;
        }
        ExecuteCommand(); //4.执行命令
    }
    return 0;
}
